package com.patsnap.data.npd.dw.repository.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.patsnap.data.npd.dw.mapper.BaseNonePatentMapper;
import com.patsnap.data.npd.dw.repository.BaseRepository;
import com.patsnap.data.npd.dw.repository.DBException;
import com.patsnap.data.npd.dw.util.MapperUtil;
import com.patsnap.one.etl.table.dto.TargetTableDto;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.reflections.Reflections;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

@Slf4j
public class BaseRepositoryImpl implements BaseRepository {
    //init log
    protected SqlSessionFactory sessionFactory;

    protected static Map<Class<? extends TargetTableDto>, Class<? extends BaseMapper>> entityClazz2MapperClazzMap;

    static {
        Reflections reflections = new Reflections("com.patsnap.data.npd.dw.mapper");
        Set<Class<? extends BaseMapper>> classSet = reflections.getSubTypesOf(BaseMapper.class);
        entityClazz2MapperClazzMap = new HashMap<>();
        for (Class<? extends BaseMapper> mapperClass : classSet) {
            if (!mapperClass.isInterface()) {
                continue;
            }
            if (mapperClass == BaseNonePatentMapper.class) {
                continue;
            }
            Type genericSuperClass = mapperClass.getGenericInterfaces()[0];
            ParameterizedType parametrizedType = (ParameterizedType) genericSuperClass;
            Type type = parametrizedType.getActualTypeArguments()[0];
            entityClazz2MapperClazzMap.put((Class<? extends TargetTableDto>) type, mapperClass);
        }
    }


    public BaseRepositoryImpl(SqlSessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    @Override
    public <T extends TargetTableDto> T queryOneByPrimaryKey(T queryPojo, Class<T> clazz) throws DBException {
        try (SqlSession session = sessionFactory.openSession(true)) {
            return queryOneByPrimaryKey(session, queryPojo, clazz);
        } catch (Exception e) {
            log.info("Error Pn: " + queryPojo.toString());
            throw new DBException(e);
        }
    }

    public <T extends TargetTableDto> T queryOneByPrimaryKey(SqlSession session, T queryPojo, Class<T> clazz) throws DBException {
        try {
            BaseMapper<T> mapper = session.getMapper(entityClazz2MapperClazzMap.get(clazz));
            QueryWrapper<T> wrapper = MapperUtil.buildQueryWrapper(queryPojo);
            T result = mapper.selectOne(wrapper);
            return result;
        } catch (Exception e) {
            throw new DBException(e);
        }
    }

    @Override
    public <T extends TargetTableDto> List<T> queryListByPrimaryKey(List<T> queryPojos, Class<T> clazz, int batch) throws DBException {
        try (SqlSession session = sessionFactory.openSession(true)) {
            batch = batch > 0 ? batch : 1;
            BaseMapper<T> mapper = session.getMapper(entityClazz2MapperClazzMap.get(clazz));
            List<T> results = new ArrayList<>();
            for (int i = 0; i < queryPojos.size(); i += batch) {
                int endIndex = Math.min(i + batch, queryPojos.size());
                List<T> batchPojos = queryPojos.subList(i, endIndex);
                QueryWrapper<T> wrapper = MapperUtil.buildQueryListWrapper(batchPojos);
                results.addAll(mapper.selectList(wrapper));
            }
            return results;
        } catch (Exception e) {
            throw new DBException(e);
        }
    }


    @Override
    public <T extends TargetTableDto> boolean upsert(T updatePojo, Class<T> clazz) throws DBException {
        SqlSession session = sessionFactory.openSession(false);
        try {
            boolean result = upsert(session, updatePojo, clazz);
            session.commit();
            return result;
        } catch (Exception e) {
            session.rollback();
            throw new DBException(e);
        } finally {
            session.close();
        }
    }

    /**
     * 该方法并不会自动提交，sql语句不会执行
     * session 必须设置为autoCommit=true或调用完该方法后再手动调用session.commit()
     */
    public <T extends TargetTableDto> boolean upsert(SqlSession session, T updatePojo, Class<T> clazz) throws DBException {
        try {
            batchUpsert(session, Collections.singletonList(updatePojo), clazz, 1);
            return true;
        } catch (Exception e) {
            throw new DBException(e);
        }
    }

    public <T extends TargetTableDto> boolean insert(SqlSession session, T updatePojo, Class<T> clazz) throws DBException {
        try {
            BaseMapper<T> mapper = session.getMapper(entityClazz2MapperClazzMap.get(clazz));
            int insertCount = mapper.insert(updatePojo);
            return insertCount > 0;
        } catch (Exception e) {
            throw new DBException(e);
        }
    }

    @Override
    public <T extends TargetTableDto> boolean insert(T updatePojo, Class<T> clazz) throws DBException {
        try (SqlSession session = sessionFactory.openSession(true)) {
            return insert(session, updatePojo, clazz);
        } catch (Exception e) {
            throw new DBException(e);
        }
    }

    @Override
    public <T extends TargetTableDto> boolean update(T updatePojo, Class<T> clazz) throws DBException {
        try (SqlSession session = sessionFactory.openSession(true)) {
            return update(session, updatePojo, clazz);
        } catch (Exception e) {
            throw new DBException(e);
        }
    }

    protected <T extends TargetTableDto> boolean update(SqlSession session, T updatePojo, Class<T> clazz) throws DBException {
        BaseMapper<T> mapper = session.getMapper(entityClazz2MapperClazzMap.get(clazz));
        UpdateWrapper<T> wrapper = MapperUtil.buildUpdateWrapper(updatePojo);
        //这里的第一个参数如果传对象，sql里面会有重复的set , 所有传null
        int updateCount = mapper.update(null, wrapper);
        return updateCount > 0;
    }

    protected <T extends TargetTableDto> boolean delete(SqlSession session, T deleteObject, Class<T> clazz) throws DBException {
        BaseMapper<T> mapper = session.getMapper(entityClazz2MapperClazzMap.get(clazz));
        QueryWrapper<T> queryPojo = MapperUtil.buildQueryWrapper(deleteObject);
        int deleteCount = mapper.delete(queryPojo);
        return deleteCount > 0;
    }

    @Override
    public <T extends TargetTableDto> boolean deleteByFields(T deleteObject, Class<T> clazz) throws DBException {
        try (SqlSession session = sessionFactory.openSession(true)) {
            return deleteByFields(session, deleteObject, clazz);
        } catch (Exception e) {
            throw new DBException(e);
        }
    }

    protected <T extends TargetTableDto> boolean deleteByFields(SqlSession session, T deleteObject, Class<T> clazz) throws DBException {
        BaseMapper<T> mapper = session.getMapper(entityClazz2MapperClazzMap.get(clazz));
        Map<String, Object> deleteMap = MapperUtil.buildDeleteMap(deleteObject);
        int deleteCount = mapper.deleteByMap(deleteMap);
        return deleteCount > 0;
    }

    @Override
    public <T extends TargetTableDto> boolean delete(T deleteObject, Class<T> clazz) throws DBException {
        try (SqlSession session = sessionFactory.openSession(true)) {
            return delete(session, deleteObject, clazz);
        } catch (Exception e) {
            throw new DBException(e);
        }
    }

    @Override
    public <T extends TargetTableDto> boolean batchInsert(List<T> insertPojos, Class<T> clazz, int batchSize) throws DBException {
        try (SqlSession session = sessionFactory.openSession(ExecutorType.BATCH, true)) {
            BaseMapper<T> mapper = session.getMapper(entityClazz2MapperClazzMap.get(clazz));
            int insertCount = 0;
            try {
                for (T insertPojo : insertPojos) {
                    mapper.insert(insertPojo);
                    if (++insertCount % batchSize == 0) {
                        session.flushStatements();
                    }
                }
                if (insertCount % batchSize != 0) {
                    session.flushStatements();
                }
            } catch (Exception e) {
                session.rollback();
                throw e;
            }
            return insertCount == insertPojos.size();
        } catch (Exception e) {
            throw new DBException(e);
        }
    }

    public <T extends TargetTableDto> boolean batchUpsert(List<T> insertPojos, Class<T> clazz, int batchSize) throws DBException {
        SqlSession session = sessionFactory.openSession(ExecutorType.BATCH, false);
        try {
            batchUpsert(session, insertPojos, clazz, batchSize);
            session.commit();
            return true;
        } catch (Exception e) {
            session.rollback();
            throw new DBException(e);
        } finally {
            session.close();
        }
    }

    /**
     * 该方法并不会自动提交，sql语句不会执行
     * session 必须设置为autoCommit=true或调用完该方法后再手动调用session.commit()
     */
    public <T extends TargetTableDto> void batchUpsert(SqlSession session, List<T> insertPojos, Class<T> clazz, int batchSize) throws DBException {
        try {
            BaseMapper<T> mapper = session.getMapper(entityClazz2MapperClazzMap.get(clazz));
            Method method;
            try {
                method = entityClazz2MapperClazzMap.get(clazz).getMethod("upsertBatchSomeColumn", List.class);
            } catch (NoSuchMethodException e) {
                throw new DBException("not support , mapper should add upsertBatchSomeColumn method", e);
            }
            for (int i = 0; i < insertPojos.size(); i += batchSize) {
                int endIndex = Math.min(i + batchSize, insertPojos.size());
                List<T> batchPojos = insertPojos.subList(i, endIndex);
                method.invoke(mapper, batchPojos);
                session.flushStatements();
            }
        } catch (Exception e) {
            throw new DBException(e);
        }
    }

    @Override
    public <T extends TargetTableDto> boolean batchUpdate(List<T> updatePojos, Class<T> clazz, int batchSize) throws DBException {
        try (SqlSession session = sessionFactory.openSession(ExecutorType.BATCH, true)) {
            BaseMapper<T> mapper = session.getMapper(entityClazz2MapperClazzMap.get(clazz));
            int updateCount = 0;
            try {
                for (T updatePojo : updatePojos) {
                    UpdateWrapper<T> wrapper = MapperUtil.buildUpdateWrapper(updatePojo);
                    mapper.update(updatePojo, wrapper);
                    if (++updateCount % batchSize == 0) {
                        session.flushStatements();
                    }
                }
                if (updateCount % batchSize != 0) {
                    session.flushStatements();
                }
            } catch (Exception e) {
                session.rollback();
                throw e;
            }
            return updateCount == updatePojos.size();
        } catch (Exception e) {
            throw new DBException(e);
        }
    }

    @Override
    public <T extends TargetTableDto> boolean batchDelete(List<T> deletePojos, Class<T> clazz, int batchSize) throws DBException {
        SqlSession session = sessionFactory.openSession(ExecutorType.BATCH, false);
        try {
            boolean delete = batchDelete(session, deletePojos, clazz, batchSize);
            session.commit();
            return delete;
        } catch (Exception e) {
            session.rollback();
            throw new DBException(e);
        } finally {
            session.close();
        }
    }

    /**
     * 该方法并不会自动提交，sql语句不会执行
     * session 必须设置为autoCommit=true或调用完该方法后再手动调用session.commit()
     */
    public <T extends TargetTableDto> boolean batchDelete(SqlSession session, List<T> deletePojos, Class<T> clazz, int batchSize) throws DBException {
        try {
            BaseMapper<T> mapper = session.getMapper(entityClazz2MapperClazzMap.get(clazz));
            int deleteCount = 0;
            for (T deletePojo : deletePojos) {
                UpdateWrapper<T> wrapper = MapperUtil.buildUpdateWrapper(deletePojo);
                mapper.delete(wrapper);
                if (++deleteCount % batchSize == 0) {
                    session.flushStatements();
                }
            }
            if (deleteCount % batchSize != 0) {
                session.flushStatements();
            }
            return deleteCount == deletePojos.size();
        } catch (Exception e) {
            throw new DBException(e);
        }
    }

    @Override
    public <T extends TargetTableDto> boolean batchDeleteByFields(List<T> deletePojos, Class<T> clazz, int batchSize) throws DBException {
        SqlSession session = sessionFactory.openSession(ExecutorType.BATCH, false);
        try {
            boolean delete = batchDeleteByFields(session, deletePojos, clazz, batchSize);
            session.commit();
            return delete;
        } catch (Exception e) {
            session.rollback();
            throw new DBException(e);
        } finally {
            session.close();
        }
    }

    /**
     * 该方法并不会自动提交，sql语句不会执行
     * session 必须设置为autoCommit=true或调用完该方法后再手动调用session.commit()
     */
    public <T extends TargetTableDto> boolean batchDeleteByFields(SqlSession session, List<T> deletePojos, Class<T> clazz, int batchSize) throws DBException {
        try {
            BaseMapper<T> mapper = session.getMapper(entityClazz2MapperClazzMap.get(clazz));
            int deleteCount = 0;
            for (T deletePojo : deletePojos) {
                Map<String, Object> deleteMap = MapperUtil.buildDeleteMap(deletePojo);
                mapper.deleteByMap(deleteMap);
                if (++deleteCount % batchSize == 0) {
                    session.flushStatements();
                }
            }
            if (deleteCount % batchSize != 0) {
                session.flushStatements();
            }
            return deleteCount == deletePojos.size();
        } catch (Exception e) {
            throw new DBException(e);
        }
    }

}
